package com.lalameow.researchstation.screen

import com.lalameow.researchstation.Blocks
import com.lalameow.researchstation.ResearchStation
import com.lalameow.researchstation.Screens
import com.lalameow.researchstation.api.technology.TechnologyCategory
import com.lalameow.researchstation.block.ResearchStationBlockEntity
import com.lalameow.researchstation.network.ResearchingTreeNetwork
import com.lalameow.researchstation.network.TechnologyCategoriesNetwork
import com.lalameow.researchstation.researching.ResearchingTree
import com.lalameow.researchstation.screen.widget.Direction
import com.lalameow.researchstation.screen.widget.ResearchTabWidget
import com.lalameow.researchstation.screen.widget.tree.ResearchingTreeWidget
import com.lalameow.researchstation.translatable
import com.lalameow.researchstation.util.bottom
import com.lalameow.researchstation.util.drawNineGridTexture
import com.lalameow.researchstation.util.drawTexture
import com.lalameow.researchstation.util.mixin.ResearchingHelper.technologies
import com.lalameow.researchstation.util.right
import com.lalameow.researchstation.util.translate
import com.mojang.blaze3d.systems.RenderSystem
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import net.fabricmc.api.EnvType
import net.fabricmc.api.Environment
import net.minecraft.client.gui.DrawableHelper
import net.minecraft.client.gui.screen.ingame.HandledScreen
import net.minecraft.client.gui.screen.ingame.HandledScreens
import net.minecraft.client.render.GameRenderer
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.client.util.math.Rect2i
import net.minecraft.client.util.math.Vector2f
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.entity.player.PlayerInventory
import net.minecraft.screen.ScreenHandler
import net.minecraft.screen.ScreenHandlerContext
import net.minecraft.text.Text
import net.minecraft.util.Identifier
import net.minecraft.util.math.MathHelper
import net.silkmc.silk.core.task.mcClientCoroutineScope
import org.lwjgl.opengl.GL11

@Environment(EnvType.CLIENT)
internal fun Screens.registerResearchStationScreen() =
    HandledScreens.register(
        RESEARCH_STATION,
        HandledScreens.Provider(::ResearchStationScreen)
    )

class ResearchStationScreenHandler constructor(
    syncId: Int,
    inventory: PlayerInventory,
    val context: ScreenHandlerContext = ScreenHandlerContext.EMPTY
) : ScreenHandler(Screens.RESEARCH_STATION, syncId) {
    val block: ResearchStationBlockEntity? = context
        .get { world, pos -> world.getBlockEntity(pos) }
        .filter { it is ResearchStationBlockEntity }
        .map { it as ResearchStationBlockEntity }
        .orElse(null)

    override fun canUse(player: PlayerEntity) = canUse(context, player, Blocks.RESEARCH_STATION)
}

@Environment(EnvType.CLIENT)
internal class ResearchStationScreen(
    handler: ResearchStationScreenHandler,
    inventory: PlayerInventory,
    title: Text?
) :
    HandledScreen<ResearchStationScreenHandler>(handler, inventory, title ?: RESEARCHING_TITLE) {

    companion object {
        val RESEARCHING_TITLE: Text = ResearchStation.translatable("gui.researching")
        private val WINDOW_TEXTURE = Identifier("textures/gui/advancements/window.png")
        val TABS_TEXTURE = Identifier("textures/gui/advancements/tabs.png")
        private const val TEXTURE_WIDTH = 252
        private const val TEXTURE_HEIGHT = 140

        // 材质偏移
        const val TOP = 18
        const val SIDE = 9
        private const val BOTTOM = 9
        private const val PADDING_V = 36
        private const val PADDING_H = 48

        // 窗口九宫
        private val WINDOW_TEXTURE_RECT = Rect2i(0, 0, TEXTURE_WIDTH, TEXTURE_HEIGHT)
        private val WINDOW_TEXTURE_INNER_RECT =
            Rect2i(SIDE + 5, TOP + 6, 223, 102)
    }

    private lateinit var job: Job
    private val researched = inventory.player.technologies
    private val block = handler.block
    private val tabs = mutableListOf<ResearchTabWidget>()
    private var tree: ResearchingTree? = null
    private var treeWidget: ResearchingTreeWidget? = null
    private var selectedTab = 0
    private var horizontalTabCount = 1
    private var verticalTabCount = 1

    private var treeUpdateJob: Job? = null

    private lateinit var windowRect: Rect2i
    private lateinit var contentRect: Rect2i

    private val currentPos = Vector2f(0.0f, 0.0f)

    private var tabTitle = title

    init {
        TechnologyCategoriesNetwork.requestPacket.send(Unit)
        x = PADDING_H
        y = PADDING_V
    }

    override fun init() {
        backgroundWidth = width - PADDING_H * 2
        backgroundHeight = height - PADDING_V * 2
        horizontalTabCount = MathHelper.floorDiv(backgroundWidth + 2, ResearchTabWidget.TAB_WIDTH + 2)
        verticalTabCount = MathHelper.floorDiv(backgroundHeight + 2, ResearchTabWidget.TAB_WIDTH + 2)
        job = mcClientCoroutineScope.launch {
            TechnologyCategoriesNetwork.categories.collect {
                refreshTabs(it)
                if (tabs.isNotEmpty()) selectTab(selectedTab)
            }
        }
        windowRect = Rect2i(0, 0, backgroundWidth, backgroundHeight)
        contentRect = Rect2i(SIDE, TOP, backgroundWidth - 2 * SIDE, backgroundHeight - TOP - BOTTOM)
    }

    private fun refreshTabs(categories: List<TechnologyCategoriesNetwork.Packet>) {
        tabs.clear()
        for (i in categories.indices) {
            var direction = Direction.TOP
            var offset = 0
            var x = 0
            var y = 0
            if (i < horizontalTabCount) {
                x = i * (ResearchTabWidget.TAB_WIDTH + 2)
                y = -ResearchTabWidget.TAB_HEIGHT + 4
                if (i == horizontalTabCount - 1) {
                    offset = 2 * direction.offset
                } else if (i != 0) {
                    offset = direction.offset
                }
            } else if (i < horizontalTabCount + verticalTabCount) {
                val current = i - horizontalTabCount
                x = -ResearchTabWidget.TAB_HEIGHT + 4
                y = current * (ResearchTabWidget.TAB_WIDTH + 2)
                direction = Direction.LEFT
                if (current == verticalTabCount - 1) {
                    offset = 2 * direction.offset
                } else if (current != 0) {
                    offset = direction.offset
                }
            } else if (i < horizontalTabCount + 2 * verticalTabCount) {
                val current = i - horizontalTabCount - verticalTabCount
                x = backgroundWidth - 4
                direction = Direction.RIGHT
                if (current == verticalTabCount - 1) {
                    offset = 2 * direction.offset
                } else if (current != 0) {
                    offset = direction.offset
                }
            } else if (i < 2 * (horizontalTabCount + verticalTabCount)) {
                val current = i - horizontalTabCount - 2 * verticalTabCount
                y = backgroundHeight - 4
                direction = Direction.BOTTOM
                if (current == horizontalTabCount - 1) {
                    offset = 2 * direction.offset
                } else if (current != 0) {
                    offset = direction.offset
                }
            }

            tabs.add(
                ResearchTabWidget(
                    categories[i],
                    itemRenderer,
                    x to y,
                    direction,
                    offset
                )
            )
        }
    }

    fun selectTab(selectedTab: Int) {
        treeUpdateJob?.cancel()
        this.selectedTab = selectedTab
        val category = tabs[selectedTab].category
        val id = category.id
        tabTitle = category.name
        ResearchingTreeNetwork.requestPacket.send(id)
        treeUpdateJob = mcClientCoroutineScope.launch {
            ResearchingTreeNetwork.categories
                .collect {
                    tree = it[id]
                    treeWidget = tree?.let { tree ->
                        ResearchingTreeWidget(
                            tree,
                            contentRect,
                            textRenderer,
                            researched
                        )
                    }
                }
        }
    }

    override fun drawForeground(matrices: MatrixStack, mouseX: Int, mouseY: Int) {
        textRenderer.draw(matrices, tabTitle, titleX.toFloat(), titleY.toFloat(), 0x404040)
        if (tree.isNullOrEmpty()) {
            DrawableHelper.drawCenteredText(
                matrices,
                textRenderer,
                ResearchStation.translatable("gui.station.loading"),
                backgroundWidth / 2,
                backgroundHeight / 2,
                0xe8e8e8
            )
        }
    }

    override fun drawBackground(matrices: MatrixStack, delta: Float, mouseX: Int, mouseY: Int) {
        with(matrices) {
            push()
            translate(x, y)
            drawTree(mouseX, mouseY)
            drawWindow()
            drawTabs(mouseX, mouseY)
            pop()
        }
    }

    private fun MatrixStack.drawWindow() {
        RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f)
        RenderSystem.enableBlend()
        RenderSystem.enableDepthTest()
        RenderSystem.setShader { GameRenderer.getPositionTexShader() }
        RenderSystem.setShaderTexture(0, WINDOW_TEXTURE)
        drawNineGridTexture(windowRect, WINDOW_TEXTURE_RECT, WINDOW_TEXTURE_INNER_RECT)
        RenderSystem.disableDepthTest()
    }

    private fun MatrixStack.drawTabs(mouseX: Int, mouseY: Int) {
        for ((index, widget) in tabs.withIndex()) {
            with(widget) {
                draw(x, y, mouseX to mouseY, selectedTab == index)
            }
        }
    }

    private fun MatrixStack.drawTree(mouseX: Int, mouseY: Int) {
        push()
        RenderSystem.enableDepthTest()

        // z=950 处整个空间覆盖一层透明色，深度测试为默认值 LESS
        translate(0.0, 0.0, 950.0)
        RenderSystem.colorMask(false, false, false, false)
        fill(this, 4680, 2260, -4680, -2260, 0x01000000)
        RenderSystem.colorMask(true, true, true, true)

        // 在 z=0 处绘制内容矩形的透明色
        translate(0.0, 0.0, -950.0)
        RenderSystem.depthFunc(GL11.GL_GEQUAL)
        fill(this, contentRect.right, contentRect.bottom, contentRect.x, contentRect.y, 0x01000000)
        RenderSystem.depthFunc(GL11.GL_LEQUAL)

        // 绘制背景
        RenderSystem.setShaderTexture(
            0,
            if (tabs.isEmpty()) TechnologyCategory.DEFAULT_BACKGROUND else tabs[selectedTab].background
        )

        val backgroundOffset = treeWidget?.currentPos?.run {
            first % 16 to second % 16
        } ?: (0 to 0)
        val backgroundRect = Rect2i(
            (contentRect.x - 16) + backgroundOffset.first,
            (contentRect.y - 16) + backgroundOffset.second,
            contentRect.width + 16,
            contentRect.height + 16
        )

        drawTexture(
            backgroundRect,
            0 to 0,
            16 to 16
        )

        if (!tree.isNullOrEmpty() && treeWidget != null) {
            with(treeWidget!!) {
                draw(x, y, mouseX to mouseY)
            }
        }

        // 在 z=-950 处绘制内容矩形的透明色
        translate(0, 0, -950)
        RenderSystem.depthFunc(GL11.GL_GEQUAL)
        RenderSystem.colorMask(false, false, false, false)
        fill(this, 4680, 2260, -4680, -2260, 0x01000000)
        RenderSystem.colorMask(true, true, true, true)
        RenderSystem.depthFunc(GL11.GL_LEQUAL)

        RenderSystem.disableDepthTest()
        pop()
    }

    override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int) =
        super.mouseClicked(mouseX, mouseY, button).also {
            for (i in tabs.indices)
                if (tabs[i].isMouseOn(x, y, mouseX.toInt() to mouseY.toInt())) selectTab(i)
        }

    override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean {
        treeWidget?.move(deltaX, deltaY)
        return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
    }

    override fun close() = super.close().also { job.cancel() }
}
